package com.dsteiner.widgets.server.dao.jdo;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;

import com.dsteiner.widgets.client.ObjectUtil;
import com.dsteiner.widgets.client.StringUtil;
import com.dsteiner.widgets.client.domain.ModelObject;
import com.dsteiner.widgets.client.domain.Sort;
import com.dsteiner.widgets.client.event.DataUpdateEvent;
import com.dsteiner.widgets.client.event.EventManager;
import com.dsteiner.widgets.client.event.EventManagerImpl;
import com.dsteiner.widgets.client.rpc.SearchRequest;
import com.dsteiner.widgets.client.rpc.SearchResult;
import com.dsteiner.widgets.server.ServerUtil;
import com.dsteiner.widgets.server.dao.BaseDataDao;
import com.dsteiner.widgets.server.dao.Change;
import com.dsteiner.widgets.server.dao.Parameter;

public class BaseDataDaoJdo implements BaseDataDao{

	private EventManager eventManager = EventManagerImpl.getInstance();

	public <T extends ModelObject> void deleteObject(String id, Class<T> objectClass) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
			Object objectById = getObjectById(id, objectClass, pm);
    		pm.deletePersistent(objectById);
    		pm.close();
        } finally {
        }		
	}
	
	public void setEventManager(EventManager eventManager) {
		this.eventManager = eventManager;
	}
	
	public <T extends ModelObject> void deleteObject(T object) {
		String id = object.getId();
		Class<T> objectClass = (Class<T>) object.getClass();
		deleteObject(id, objectClass);
	}

	public <T extends ModelObject> void deleteObjects(Class<T> objectClass, List<Parameter> parametersList) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			List<T> objects = getObjects(objectClass, parametersList, null, null, pm);
			pm.deletePersistentAll(objects);
			pm.close();
		} finally {
		}		
	}
	
	public synchronized <T extends ModelObject> T getObjectById(String id, Class<T> objectClass) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
        	return (T) getObjectById(id, objectClass, pm);
        } finally {
            pm.close();
        }		
	}

	public <T extends ModelObject> T getObjectById(String id, Class<T> objectClass, PersistenceManager pm) {
		Query query = pm.newQuery(objectClass);
		query.setFilter("id == idParam");
		query.declareParameters("String idParam");
		query.setRange(0, 1);

		if(id == null)
			return null;
		List<T> users = (List<T>) query.execute(id);
		if(users != null && !users.isEmpty())
			return users.get(0);
		else
			return null;
	}
	
	public <T extends ModelObject> List<String> getObjectIds(Class<T> clazz) {
		try{
			PersistenceManager pm = PMF.get().getPersistenceManager();
			Query query = pm.newQuery(clazz);
			query.setResult("id");
			List<String> list =  (List<String>) query.execute();
			return list;
		}catch(Exception ex){
			ex.printStackTrace();
		}
		return null;
	}
	
	public <T extends ModelObject> List<T> getObjects(Class<T> classObj) {
		return getObjects(classObj, null);
	}

	public <T extends ModelObject> List<T> getObjects(Class<T> classObj, List<Parameter> parametersList) {
		return getObjects(classObj, parametersList, null);
	}
	
	public <T extends ModelObject> List<T> getObjects(Class<T> classObj, List<Parameter> parametersList, String ordering) {
		return getObjects(classObj, parametersList, ordering, null);
	}
	
	public <T extends ModelObject> List<T> getObjects(Class<T> classObj, List<Parameter> parametersList, String ordering, 
			Integer limit) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
        	return getObjects(classObj, parametersList, ordering, limit, pm);
        } finally {
            pm.close();
        }
	}

	public synchronized <T extends ModelObject> List<T> getTopModelObjects(Class<T> modelObjectClass, String ordering) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
        	Query query = pm.newQuery(modelObjectClass);
            query.setOrdering(ordering);
            query.setRange(0, 3);

            List<T> topModelObject =  (List<T>)query.execute();
            topModelObject = (List<T>) pm.detachCopyAll(topModelObject);
			return topModelObject;
        } finally {
            pm.close();
        }		
	}
	
	public <T extends ModelObject> boolean isExistant(Class<T> classObj, List<Parameter> parametersList) {
		List results = getObjects(classObj, parametersList);		
        return results != null && !results.isEmpty();
	}

	public synchronized <T extends ModelObject> Collection<T> save(Collection<T> objects) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			pm.makePersistentAll(objects);
			pm.close();
			return objects;
		} finally {
		}		
	}
	
	public synchronized <T extends ModelObject> T save(T object) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(object);
            pm.close();
            return object;
        } finally {
        }		
	}

	public <T extends ModelObject> SearchResult<T> search(SearchRequest searchRequest) {
		SearchResult<T> searchResult = new SearchResult<T>();
		try{
			PersistenceManager pm = PMF.get().getPersistenceManager();
			Sort sort = searchRequest.getSort();
			int offset = searchRequest.getOffset();
			int limit = searchRequest.getLimit();
			String text = searchRequest.getText();
			System.out.println("BaseDataDaoJdo.search():"+searchRequest);
			Class objectClass = Class.forName(searchRequest.getClassName());
			List<T> results = new ArrayList<T>();
			if(searchRequest.isAll()){
				Query query = pm.newQuery(objectClass);
				List<T> execute = (List<T>) query.execute();
				List<T> queryResult = (List<T>) pm.detachCopyAll(execute);
				results.addAll(queryResult);
			}
			else if(searchRequest.isBetween()){
				Query query = pm.newQuery(objectClass);
				try{
						List<T> fromResults = new ArrayList<T>();
						Object queryObj = new SimpleDateFormat("dd/MM/yyyy").parseObject(text);
						String fromFilter = searchRequest.getFromFieldName()+" <= dateParam";
						String parameters = "java.util.Date dateParam";
						doQuery(fromResults, fromFilter, parameters, objectClass, queryObj);
						List<T> toResults = new ArrayList<T>();
						String toFilter = searchRequest.getToFieldName()+" >= dateParam";
						doQuery(toResults, toFilter, parameters, objectClass, queryObj);
						for (T t : fromResults) {
							Object property = ServerUtil.getProperty(searchRequest.getFromFieldName(), t);
							if(toResults.contains(t) || queryObj.equals(property))
								results.add(t);
						}
					}catch(ParseException parseException){
					}
			}
			else{
				Set<Field> fields = ServerUtil.getAnnotationFields(objectClass, 
						Class.forName(searchRequest.getAnnotationClassName()));
				
				for (Field field : fields) {
					Object queryObj = text;
					String type = "String";
					String comparator = "==";
					boolean isStringType = String.class.equals(field.getType());
					boolean isDateType = Date.class.equals(field.getType());
					if(isDateType){
						try{
						queryObj = new SimpleDateFormat("dd/MM/yyyy").parseObject(text);
						}catch(ParseException parseException){
							continue;
						}
						type = "java.util.Date";
						comparator = "==";
					}
					
					if(searchRequest.isEquals()){
						String filter = field.getName()+" " + comparator + " textParam";
						String parameters = type+" textParam";
						doQuery(results, filter, parameters, objectClass, queryObj);
						if(isStringType){
							queryObj = StringUtil.toUpperCaseFirstChar(text);
							doQuery(results, filter, parameters, objectClass, queryObj);
						}
					}
					else if(searchRequest.isPrefix()){
						String filter = field.getName()+" >= textParam1 && "+field.getName()+" < textParam2";
						String parameters = type+" textParam1, "+type+" textParam2";
						if(isStringType)
							queryObj = text.toLowerCase();
						doQuery(results, filter, parameters, objectClass, queryObj, queryObj + "\ufffd");
						if(isStringType){
							queryObj = StringUtil.toUpperCaseFirstChar(text);
							doQuery(results, filter, parameters, objectClass, queryObj, queryObj + "\ufffd");
						}
						if(isStringType){
							queryObj = text;
							doQuery(results, filter, parameters, objectClass, queryObj, queryObj + "\ufffd");
						}
					}
				}
			}
			
			if(sort != null){
				results = ServerUtil.sort(results, sort.getField(), sort.isAsc());
			}
			List<T> subList = ObjectUtil.subList(results, offset, offset + limit);
			System.out.println("BaseDataDaoJdo.search():results:"+subList);
			searchResult.setObjects(subList);
			searchResult.setCount(results.size());
			return searchResult;
		}catch(Exception ex){
			ex.printStackTrace();
		}
		return searchResult;
	}
	
	public <T extends ModelObject> Map<String, Change> updateObject(T object) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Map<String, Change> changes = new HashMap<String, Change>();
		try {
			T objectToUpdate = (T) getObjectById(object.getId(), (Class<T>) object.getClass(), pm);
			Class<T> classObject = (Class<T>) objectToUpdate.getClass();
			Method[] fields = classObject.getDeclaredMethods();
			for (Method setter : fields) {
				String name = setter.getName();
				if(!name.startsWith("set") || "getId".equalsIgnoreCase(name))
					continue;
				Method getter = classObject.getDeclaredMethod("get"+name.replaceAll("set", ""), null);
				Object value = getter.invoke(object, null);
				Object oldValue = getter.invoke(objectToUpdate, null);
				setter.invoke(objectToUpdate, new Object[]{value});
				if(!ObjectUtil.equals(value, oldValue)){
					String propertyName = StringUtil.toLowerCaseFirstChar(name.replaceAll("set", ""));
					changes.put(propertyName, new Change(propertyName, oldValue, value));
				}
			}
			eventManager.fireEvent(new DataUpdateEvent(objectToUpdate));
			System.out.println("BaseDataDaoJdo.updateObject()");
			pm.detachCopy(objectToUpdate);
		}catch(Exception ex){
			ex.printStackTrace();
		}finally {
			pm.close();
		}	
		return changes;
	}
	
	protected void formatParameters(List<Parameter> parametersList, Query query, Map<String, Object> parametersMap) {
		StringBuffer filterBuffer = new StringBuffer();
		StringBuffer parametersBuffer = new StringBuffer();
		formatParameters(parametersList, filterBuffer, parametersBuffer, parametersMap);
		query.setFilter(filterBuffer.toString());
		query.declareParameters(parametersBuffer.toString());
	}

	protected void formatParameters(List<Parameter> parametersList, StringBuffer filterBuffer,
				StringBuffer parametersBuffer, Map<String, Object> parametersMap) {
		for (Iterator<Parameter> iterator = parametersList.iterator(); iterator.hasNext();) {
			Parameter parameter = iterator.next();
			String parameterName = parameter.getName();
			if(!parameterName.contains("Param"))
				parameterName += "Param";
			parametersMap.put(parameterName, parameter.getValue());
			filterBuffer.append(parameterName.replace("Param", "").replace("_", "."))
			.append(" ").append(parameter.getComparator()).append(" ").append(parameterName);
			parametersBuffer.append(parameter.getType()).append(" ").append(parameterName);
			if(iterator.hasNext()){
				filterBuffer.append(" && ");
				parametersBuffer.append(", ");
			}
		}
	}

	private <T extends ModelObject> void doQuery(List<T> results, String filter, String parameters, Class objectClass, Object queryObj) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(objectClass);
		query.setFilter(filter);
		query.declareParameters(parameters);
		List<T> queryResult = (List<T>) query.execute(queryObj);
		for (T result : queryResult) {
			if(!results.contains(result))
				results.add(result);
		}
	}
	
	private <T extends ModelObject> void doQuery(List<T> results, String filter, String parameters, Class objectClass, 
			Object queryObj, Object queryObj2) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(objectClass);
		query.setFilter(filter);
		query.declareParameters(parameters);
		List<T> queryResult = (List<T>) query.execute(queryObj, queryObj2);
		for (T result : queryResult) {
			if(!results.contains(result))
				results.add(result);
		}
	}

	private <T> List<T> getObjects(Class<T> classObj, List<Parameter> parametersList, String ordering, Integer limit,
			PersistenceManager pm) {
		List<T> results = new ArrayList<T>();
		Query query = pm.newQuery(classObj);
		Map<String, Object> parametersMap = new HashMap<String, Object>();
		if(parametersList != null && !parametersList.isEmpty()){
			StringBuffer filterBuffer = new StringBuffer();
			StringBuffer parametersBuffer = new StringBuffer();
			formatParameters(parametersList, filterBuffer, parametersBuffer, parametersMap);
			query.setFilter(filterBuffer.toString());
			query.declareParameters(parametersBuffer.toString());
		}
		if(limit != null)
			query.setRange(0, limit);
		if(ordering != null)
			query.setOrdering(ordering);
		List<T> queryResults = (List<T>) query.executeWithMap(parametersMap);
		for (T object : queryResults) {
			results.add(object);
		}
		return results;
	}
}
